/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.log.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.http.useragent.Browser;
import cn.hutool.http.useragent.OS;
import cn.hutool.http.useragent.Platform;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.iwindplus.boot.domain.constant.CommonConstant;
import com.iwindplus.boot.domain.constant.CommonConstant.UserConstant;
import com.iwindplus.boot.domain.context.HeaderContextHolder;
import com.iwindplus.boot.domain.enums.BizCodeEnum;
import com.iwindplus.boot.domain.exception.BizException;
import com.iwindplus.boot.domain.vo.TraceInfoVO;
import com.iwindplus.boot.domain.vo.UserBaseVO;
import com.iwindplus.boot.mongo.service.impl.MongoBaseServiceImpl;
import com.iwindplus.boot.util.AddressUtil;
import com.iwindplus.boot.util.PatternUtil;
import com.iwindplus.boot.util.domain.vo.AddressVO;
import com.iwindplus.log.domain.dto.OperationLogDTO;
import com.iwindplus.log.domain.dto.OperationLogNewestDTO;
import com.iwindplus.log.domain.dto.OperationLogSearchDTO;
import com.iwindplus.log.domain.vo.OperationLogExtendVO;
import com.iwindplus.log.domain.vo.OperationLogPageVO;
import com.iwindplus.log.domain.vo.OperationLogVO;
import com.iwindplus.log.server.model.OperationLogDO;
import com.iwindplus.log.server.service.OperationLogService;
import com.iwindplus.mgt.client.power.UserClient;
import com.iwindplus.mgt.domain.vo.power.UserVO;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.StringUtil;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 操作日志业务层接口实现类.
 *
 * @author zengdegui
 * @since 2024/4/10
 */

@Slf4j
@Service
@CacheConfig(cacheNames = {"operationLog"})
@Transactional(rollbackFor = Exception.class)
public class OperationLogServiceImpl extends MongoBaseServiceImpl<OperationLogDO>
    implements OperationLogService {

    @Resource
    private UserClient userClient;

    @CacheEvict(allEntries = true)
    @Override
    public boolean saveData(OperationLogDTO entity, UserBaseVO userInfo, TraceInfoVO traceInfo) {
        if (ObjectUtil.isEmpty(entity.getBizNumber())) {
            entity.setBizNumber(StringUtil.getUUID());
        }
        this.buildSystemInfo(entity);
        this.buildLocation(entity, traceInfo);
        if (Objects.isNull(entity.getUserId())) {
            entity.setUserId(userInfo.getUserId());
        }
        if (Objects.isNull(entity.getOrgId())) {
            entity.setOrgId(userInfo.getOrgId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getQueryParams())) {
            String str = HtmlUtil.unescape(entity.getQueryParams());
            entity.setQueryParams(str);
        }
        if (CharSequenceUtil.isNotBlank(entity.getRequestBody())) {
            String str = HtmlUtil.unescape(entity.getRequestBody());
            entity.setRequestBody(str);
        }
        if (CharSequenceUtil.isNotBlank(entity.getResponseBody())) {
            String str = HtmlUtil.unescape(entity.getResponseBody());
            entity.setResponseBody(str);
        }
        final OperationLogDO model = BeanUtil.copyProperties(entity, OperationLogDO.class);
        super.save(model);
        entity.setId(model.getId());
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean removeByIds(List<String> ids) {
        final Criteria criteria = Criteria.where(CommonConstant.DbConstant.ID).in(ids);
        final Query query = Query.query(criteria);
        List<OperationLogDO> data = super.list(query);
        if (CollUtil.isEmpty(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        super.removeByIds(ids, false);
        return Boolean.TRUE;
    }

    @Override
    public IPage<OperationLogPageVO> page(OperationLogSearchDTO entity) {
        final PageDTO<OperationLogDO> page = new PageDTO<>(entity.getCurrent(), entity.getSize());
        Criteria criteria = new Criteria();
        if (Objects.nonNull(entity.getOrgId())) {
            criteria.and("orgId").is(entity.getOrgId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getBizNumber())) {
            criteria.and("bizNumber").is(entity.getBizNumber().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getOperateName())) {
            criteria.and("operateName").is(entity.getOperateName().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getJobNumber())) {
            Long userId = GatewayLogServiceImpl.getUserIdByJobNumber(userClient, entity.getJobNumber());
            entity.setUserId(userId);
        } else if (CharSequenceUtil.isNotBlank(entity.getMobile())) {
            Long userId = GatewayLogServiceImpl.getUserIdByMobile(userClient, entity.getMobile());
            entity.setUserId(userId);
        }
        if (Objects.nonNull(entity.getUserId())) {
            criteria.and(CommonConstant.UserConstant.USER_ID).is(entity.getUserId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getTraceId())) {
            criteria.and("traceId").is(entity.getTraceId().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getIp())) {
            criteria.and("ip").regex(PatternUtil.getPatternLike(entity.getIp()));
        }
        if (CharSequenceUtil.isNotBlank(entity.getProvince())) {
            criteria.and("province").is(entity.getProvince().trim());
        }
        if (CharSequenceUtil.isNotBlank(entity.getCity())) {
            criteria.and("city").is(entity.getCity().trim());
        }
        Query query = Query.query(criteria);
        List<OrderItem> orders = page.getOrders();
        if (CollUtil.isEmpty(orders)) {
            orders = new ArrayList<>(10);
            OrderItem orderItem = OrderItem.desc("modifiedTime");
            orders.add(orderItem);
            page.setOrders(orders);
        }
        final IPage<OperationLogDO> modelPage = super.page(page, query);
        final IPage<OperationLogPageVO> result = modelPage.convert(model -> BeanUtil.copyProperties(model, OperationLogPageVO.class));
        List<OperationLogPageVO> records = result.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            this.buildUserInfo(result, records);
        }
        return result;
    }

    @Cacheable(key = "#root.methodName + '_' + #p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public OperationLogExtendVO getDetail(String id) {
        OperationLogDO data = super.getById(id);
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        OperationLogExtendVO result = BeanUtil.copyProperties(data, OperationLogExtendVO.class);
        result.setMobile(GatewayLogServiceImpl.getMobileByUserId(userClient, result.getUserId()));
        return result;
    }

    @Override
    public OperationLogVO getNewestByCondition(OperationLogNewestDTO entity) {
        Query query = Query.query(Criteria.where(UserConstant.ORG_ID).is(entity.getOrgId())
                .and(CommonConstant.UserConstant.USER_ID).is(entity.getCurrentUserId())
                .and("bizNumber").is(entity.getBizNumber())
                .and("bizType").is(entity.getBizType())
                .and("operationType").is(entity.getOperateType())
                .and("operationName").is(entity.getOperateName()))
            .with(Sort.by(Sort.Direction.DESC, CommonConstant.DbConstant.MODIFIED_TIME))
            .limit(1);
        final OperationLogDO data = super.getOne(query);
        if (Objects.isNull(data)) {
            return new OperationLogVO();
        }
        return BeanUtil.copyProperties(data, OperationLogVO.class);
    }

    private void buildSystemInfo(OperationLogDTO entity) {
        if (CharSequenceUtil.isBlank(entity.getPlatformName())
            && CharSequenceUtil.isBlank(entity.getOsName())
            && CharSequenceUtil.isBlank(entity.getBrowserName())) {
            final String userAgentStr = Optional.ofNullable(HeaderContextHolder.getContext())
                .map(m -> m.get(HttpHeaders.USER_AGENT)).orElse(null);
            if (CharSequenceUtil.isNotBlank(userAgentStr)) {
                final UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
                entity.setPlatformName(Optional.ofNullable(userAgent).map(UserAgent::getPlatform).map(Platform::getName).orElse(null));
                entity.setOsName(Optional.ofNullable(userAgent).map(UserAgent::getOs).map(OS::getName).orElse(null));
                entity.setBrowserName(Optional.ofNullable(userAgent).map(UserAgent::getBrowser).map(Browser::getName).orElse(null));
            }
        }
    }

    private void buildLocation(OperationLogDTO entity, TraceInfoVO traceInfo) {
        if (CharSequenceUtil.isBlank(entity.getIp())) {
            entity.setIp(traceInfo.getRealIp());
        }
        if (CharSequenceUtil.isBlank(entity.getTraceId())) {
            entity.setTraceId(traceInfo.getTraceId());
        }
        if (CharSequenceUtil.isNotBlank(entity.getIp())) {
            AddressVO result = AddressUtil.getAddress(entity.getIp());
            entity.setProvince(Optional.ofNullable(result).map(AddressVO::getProvince).orElse(null));
            entity.setCity(Optional.ofNullable(result).map(AddressVO::getCity).orElse(null));
        }
    }

    private void buildUserInfo(IPage<OperationLogPageVO> result, List<OperationLogPageVO> records) {
        final List<Long> ids = records.stream().map(OperationLogPageVO::getUserId).toList();
        if (CollUtil.isNotEmpty(ids)) {
            final List<UserVO> userList = GatewayLogServiceImpl.getUserList(userClient, ids);
            if (CollUtil.isNotEmpty(userList)) {
                final List<OperationLogPageVO> list = records.stream().map(m -> {
                    userList.stream().forEach(p -> {
                        if (m.getUserId().equals(p.getId())) {
                            m.setJobNumber(p.getJobNumber());
                            m.setMobile(p.getMobile());
                        }
                    });
                    return m;
                }).toList();
                result.setRecords(list);
            }
        }
    }
}
